package ace.cmp.spring.data.jdbc.querydsl.rsql.core;

import ace.cmp.spring.data.jdbc.querydsl.core.QuerydslUtils;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.ComparableExpression;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.sql.RelationalPath;
import cz.jirutka.rsql.parser.ast.ComparisonNode;
import cz.jirutka.rsql.parser.ast.RSQLOperators;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author caspar
 * @date 2023/4/14 10:54
 */
public class RsqlPredicateResolver {
  private final List<RelationalPath<?>> relationalPaths;

  public RsqlPredicateResolver(RelationalPath<?>... relationalPaths) {
    this.relationalPaths = Arrays.asList(relationalPaths);
  }

  public RsqlPredicateResolver(Class<?>... entityTypes) {
    this.relationalPaths =
        Arrays.stream(entityTypes)
            .map(QuerydslUtils::getRelationalPathBaseFromEntityType)
            .collect(Collectors.toList());
  }

  public Predicate resolve(ComparisonNode comparisonNode) {
    SimpleExpression path = this.findColumn(comparisonNode);
    if (path == null) {
      return null;
    }
    if (RSQLOperators.EQUAL.equals(comparisonNode.getOperator())) {
      return path.eq(comparisonNode.getArguments().get(0));
    }
    if (RSQLOperators.NOT_EQUAL.equals(comparisonNode.getOperator())) {
      return path.ne(comparisonNode.getArguments().get(0));
    }
    if (RSQLOperators.GREATER_THAN.equals(comparisonNode.getOperator())) {
      return ((ComparableExpression) path).gt(comparisonNode.getArguments().get(0));
    }
    if (RSQLOperators.GREATER_THAN_OR_EQUAL.equals(comparisonNode.getOperator())) {
      return ((ComparableExpression) path).goe(comparisonNode.getArguments().get(0));
    }
    if (RSQLOperators.LESS_THAN.equals(comparisonNode.getOperator())) {
      return ((ComparableExpression) path).lt(comparisonNode.getArguments().get(0));
    }
    if (RSQLOperators.LESS_THAN_OR_EQUAL.equals(comparisonNode.getOperator())) {
      return ((ComparableExpression) path).loe(comparisonNode.getArguments().get(0));
    }
    if (RSQLOperators.IN.equals(comparisonNode.getOperator())) {
      return path.in(comparisonNode.getArguments());
    }
    if (RSQLOperators.NOT_IN.equals(comparisonNode.getOperator())) {
      return path.notIn(comparisonNode.getArguments());
    }
    if (RsqlQuerydslOperators.LIKE.equals(comparisonNode.getOperator())) {
      return ((StringExpression) path).like("%" + comparisonNode.getArguments().get(0) + "%");
    }
    if (RsqlQuerydslOperators.LIKE_RIGHT.equals(comparisonNode.getOperator())) {
      return ((StringExpression) path).like(comparisonNode.getArguments().get(0) + "%");
    }
    return null;
  }

  private SimpleExpression findColumn(ComparisonNode comparisonNode) {

    boolean hasSchemaName = comparisonNode.getSelector().contains(".");
    String comparisonSchemaName =
        hasSchemaName ? comparisonNode.getSelector().split("\\.")[0] : null;
    SimpleExpression result =
        (SimpleExpression)
            this.relationalPaths.stream()
                // .filter(relationalPath -> !hasSchemaName ||
                // relationalPath.getSchemaName().equalsIgnoreCase(comparisonSchemaName))
                .map(RelationalPath::getColumns)
                .flatMap(Collection::stream)
                .filter(
                    column -> {
                      if (this.relationalPaths.size() > 1) {
                        return ((Path<?>) column).toString().equals(comparisonNode.getSelector());
                      } else {
                        return ((Path<?>) column)
                            .getMetadata()
                            .getName()
                            .equals(comparisonNode.getSelector());
                      }
                    })
                .findFirst()
                .orElse(null);

    return result;
  }
}
